package com.sparrow.resource.service;


import com.google.common.base.Preconditions;
import com.sparrow.common.mergeupdate.MergeUpdateBox;
import com.sparrow.common.mergeupdate.impl.DelayMergeBox;
import com.sparrow.common.thread.IdExecutorGroup;
import com.sparrow.common.thread.factory.SimpleThreadFactory;
import com.sparrow.common.util.*;
import com.sparrow.common.reload.ReloadMonitor;
import com.sparrow.event.api.EventBus;
import com.sparrow.event.model.Event;
import com.sparrow.resource.annotation.*;
import com.sparrow.resource.cache.store.impl.DefaultDataStore;
import com.sparrow.resource.cache.ResourceCache;
import com.sparrow.resource.cache.store.impl.Int2ObjectMapDataStore;
import com.sparrow.resource.event.ResourceReloadedEvent;
import com.sparrow.resource.global.IGlobalItem;
import com.sparrow.resource.listener.AfterResourceLoad;
import com.sparrow.resource.reader.ResourceReader;
import com.sparrow.resource.reader.impl.ExcelResourceReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.lang.reflect.*;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 配置服务类
 */
public class ResourceService implements  BeanFactoryAware, BeanDefinitionRegistryPostProcessor {

    private static Logger logger = LoggerFactory.getLogger(ResourceService.class);

    /**
     * 资源缓存map
     */
    private volatile Map<Class<?>, ResourceCache> resourceCacheMap = new ConcurrentHashMap<>();
    /**
     * 全局资源缓存
     */
    private volatile ResourceCache<String, ? extends IGlobalItem> globalResourceCache;
    /**
     * 路径映射资源class类
     */
    private Map<Path, Class<?>> pathToClassMap;
    /**
     * 资源reader实现
     */
    private final ResourceReader reader;
    /**
     * 热更监控
     */
    private ReloadMonitor reloadMonitor;
    /**
     * 包信息
     */
    private String packageInfo;

    private DefaultListableBeanFactory beanFactory;

    private EventBus eventBus;

    private ConcurrentHashMap<Path, Boolean> batchReloadPaths = new ConcurrentHashMap<>();

    private MergeUpdateBox mergeUpdater = new DelayMergeBox();

    private IdExecutorGroup idExecutorGroup;

    public ResourceService(EventBus eventBus, IdExecutorGroup idExecutorGroup) {
        this.reader = new ExcelResourceReader();
        this.reloadMonitor = ReloadMonitor.getInstance();
        this.pathToClassMap = new HashMap<>();
        this.eventBus = eventBus;
        this.idExecutorGroup = idExecutorGroup;
    }

    public void setPackagePath(String packageInfo) {
        this.packageInfo = packageInfo;
    }

    /**
     * 构建配置缓存
     * @param packageArray
     * @return
     */
    private Map<Class<?>, ResourceCache> buildCacheMap(String packageArray) {
        Map<Class<?>, ResourceCache> resourceCacheMap = new HashMap<>();
        Set<String> classSet = PackageUtil.scanClassNames(packageArray);
        for (String clazzStr : classSet) {
            Class<?> clazz;
            try {
                clazz = Class.forName(clazzStr);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            if (!clazz.isAnnotationPresent(Resource.class)) {
                continue;
            }

            ResourceCache cache = getOrCreateCache(clazz);
            resourceCacheMap.put(clazz, cache);
        }
        //并发8条线程 加载所有配置
        int cacheSize = resourceCacheMap.size();
        if (cacheSize > 0) {
            ArrayBlockingQueue<ResourceCache> queue = new ArrayBlockingQueue<>(cacheSize);
            for (ResourceCache cache : resourceCacheMap.values()) {
                queue.offer(cache);
            }

            int concurrentLevel = 8;
            CountDownLatch countDownLatch = new CountDownLatch(queue.size());
            ExecutorService executorService = Executors.newFixedThreadPool(concurrentLevel, new SimpleThreadFactory("ResourceLoader"));
            for (int i = 0; i < concurrentLevel; i++) {
                executorService.execute(() -> {
                    while (true) {
                        ResourceCache poll = queue.poll();
                        if (poll == null) {
                            return;
                        }
                        loadData(poll);
                        countDownLatch.countDown();
                    }
                });
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            executorService.shutdown();
        }
        postHandle(resourceCacheMap);
        check(resourceCacheMap);
        return resourceCacheMap;
    }

    /**
     * 后置处理
     * @param resourceCacheMap
     */
    private void postHandle(Map<Class<?>, ResourceCache> resourceCacheMap) {

    }

    /**
     * 返回拓扑排序后的 Class 列表（被依赖的优先） Kahn 算法
     */
    private List<Class<?>> topologicalSort(Set<Class<?>> resourceClasses) {
        Map<Class<?>, Set<Class<?>>> graph = new HashMap<>();
        Map<Class<?>, Integer> indegree = new HashMap<>();

        for (Class<?> clazz : resourceClasses) {
            Resource annotation = clazz.getAnnotation(Resource.class);
            if (annotation == null) {
                continue;
            }
            List<Class<?>> dependencies = findDependencies(clazz);

            graph.computeIfAbsent(clazz, k -> new HashSet<>());
            indegree.putIfAbsent(clazz, 0);

            for (Class<?> dep : dependencies) {
                graph.computeIfAbsent(dep, k -> new HashSet<>()).add(clazz);
                indegree.put(clazz, indegree.getOrDefault(clazz, 0) + 1);
                indegree.putIfAbsent(dep, 0);
            }
        }

        // 拓扑排序：Kahn 算法
        Queue<Class<?>> queue = new LinkedList<>();
        for (Class<?> clazz : indegree.keySet()) {
            if (indegree.get(clazz) == 0) {
                queue.add(clazz);
            }
        }

        List<Class<?>> sorted = new ArrayList<>();
        while (!queue.isEmpty()) {
            Class<?> current = queue.poll();
            sorted.add(current);
            for (Class<?> neighbor : graph.getOrDefault(current, Collections.emptySet())) {
                indegree.put(neighbor, indegree.get(neighbor) - 1);
                if (indegree.get(neighbor) == 0) {
                    queue.add(neighbor);
                }
            }
        }

        if (sorted.size() != resourceClasses.size()) {
            throw new RuntimeException("配置存在循环依赖，无法拓扑排序！");
        }

        return sorted;
    }

    private List<Class<?>> findDependencies(Class<?> clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        List<Class<?>> list = new ArrayList<>();
        for (Method m : methods) {
            if (!m.isAnnotationPresent(PostLoaded.class)) {
                continue;
            }

        }
        return list;
    }


    /**
     * 配置检查 调用@Check注解的方法 例如public void check(ResourceCache<Integer, MonsterResource))
     * @param resourceCacheMap
     */
    private void check(Map<Class<?>, ResourceCache> resourceCacheMap) {
        //配置实例级别的检查 会检查单个配置实例对象
        checkInstance(resourceCacheMap);
    }

    private void checkInstance(Map<Class<?>, ResourceCache> resourceCacheMap) {
        for (Map.Entry<Class<?>, ResourceCache> e : resourceCacheMap.entrySet()) {
            Class<?> clazz = e.getKey();
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(Check.class)) {
                    continue;
                }

                Type[] genericParameterTypes = method.getGenericParameterTypes();
                Type pt = null;
                List<ResourceCache> params = new ArrayList<>();
                for (Type genericParameterType : genericParameterTypes) {
                    //ParameterizedType:表示一种参数化类型，比如ResourceCache<Integer, MonsterResource)
                    //通过泛型参数拿到对应的cache
                    if(genericParameterType instanceof ParameterizedType){
                        Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                        pt = actualTypeArguments[1];
                        ResourceCache tempCache = resourceCacheMap.get(pt);
                        if (tempCache == null) {
                            throw new RuntimeException("Cannot find ResourceCache for type: " + pt);
                        }
                        params.add(tempCache);
                    }
                }

                for (Object target : e.getValue().values()) {
                    try {
                        method.invoke(target, params.toArray(new ResourceCache[0]));
                    } catch (Exception ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }
            for (Object target : e.getValue().values()) {
                validateField(target);
            }

        }
    }

    /**
     * 校验配置实例字段
     * @param obj
     */
    private void validateField(Object obj) {
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            try {
                Object value = field.get(obj);
                // 处理 @NotNull
                if (field.isAnnotationPresent(NotNull.class)) {
                    if (value == null) {
                        NotNull notNull = field.getAnnotation(NotNull.class);
                        throw new RuntimeException(String.format("%s ID=%d %s不能为空", clazz.getSimpleName(), getIdVal(clazz, obj), field.getName()));
                    }
                }
                // 处理 @Range
                if (field.isAnnotationPresent(Range.class)) {
                    Range range = field.getAnnotation(Range.class);
                    if (value instanceof Number) {
                        long val = ((Number) value).longValue();
                        if (val < range.min() || val > range.max()) {
                            throw new RuntimeException(
                                    String.format("%s ID=%d %s的值%d不在范围[%d, %d]内",
                                            clazz.getSimpleName(), getIdVal(clazz, obj), field.getName(), val, range.min(), range.max()));
                        }
                    } else {
                        throw new RuntimeException(
                                String.format("%s ID=%d .%s的字段类型不是数字类型，无法应用@Range校验",
                                        clazz.getSimpleName(), getIdVal(clazz, obj), field.getName()));
                    }
                }

            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public Object getIdVal(Class<?> modelClass, Object target) {
        Field[] fields = ReflectionUtil.getFieldsWithAnnotation(modelClass, ID.class);
        Field idField = fields[0];
        ReflectionUtils.makeAccessible(idField);
        Object key = null;
        try {
            key = idField.get(target);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return key;
    }

    /**
     * 加载单个配置表
     * @param cache
     */
    private void loadData(ResourceCache cache) {
        ResourceReader reader = this.reader;
        File file = new File(cache.getPath().toString());
        List<?> modelList = reader.read(file, cache.getModelClass());
        Field[] fields = ReflectionUtil.getFieldsWithAnnotation(cache.getModelClass(), ID.class);
        Field idField = fields[0];
        ReflectionUtils.makeAccessible(idField);
        Map<Object, Object> modelMap = new HashMap<>();
        for (Object value : modelList) {
            try {
                Object key = idField.get(value);
                if (value instanceof AfterResourceLoad) {
                    ((AfterResourceLoad) value).afterLoad();
                }
                Object preValue = modelMap.put(key, value);
                Preconditions.checkState(preValue == null, "配置 " + cache.getModelClass() + " id重复 id=" + key);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        Class<?> type = idField.getType();
        if (type == int.class || type == Integer.class) {
            cache.setDataStore(new Int2ObjectMapDataStore(modelMap, cache.buildIndexData()));
        } else {
            cache.setDataStore(new DefaultDataStore(modelMap, cache.buildIndexData()));
        }
        logger.info("load cache:{}", cache.getModelClass());
    }

    private ResourceCache getOrCreateCache(Class<?> clazz) {
        ResourceCache resourceCache = resourceCacheMap.computeIfAbsent(clazz, k -> {
            //注册bean定义
            String beanName = regBeanDefinition(clazz);
            ResourceCache c = (ResourceCache) beanFactory.getBean(beanName);
            initResourceBean(c, clazz);
            return c;
        });
        return resourceCache;
    }

    private void initResourceBean(ResourceCache c, Class<?> clazz) {
        File file = getFile(clazz);
        c.setPath(file.toPath().toAbsolutePath().normalize());
        c.setModelClass(clazz);
        if (IGlobalItem.class.isAssignableFrom(clazz)) {
            globalResourceCache = c;
        }
    }

    private File getFile(Class<?> clazz) {
        Resource annotation = clazz.getAnnotation(Resource.class);
        Field[] fields = ReflectionUtil.getFieldsWithAnnotation(clazz, ID.class);
        Preconditions.checkState(fields != null && fields.length == 1, clazz + " 沒有@ID注解字段");
        String anVal = annotation.value();
        String path = anVal;
        File file = FileUtil.getFile(path);
        return file;
    }

    private String regBeanDefinition(Class<?> clazz) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(ResourceCache.class);
        beanDefinition.setScope("singleton");

        Class<?> idClass = getIdClass(clazz);

        ResolvableType rt = ResolvableType.forClassWithGenerics(ResourceCache.class, idClass, clazz);
        beanDefinition.setTargetType(rt);
        beanDefinition.setBeanClass(rt.getRawClass());
        String beanName = StringUtils.uncapitalize(clazz.getSimpleName() + "Cache");
        beanFactory.registerBeanDefinition(beanName, beanDefinition);
        return beanName;
    }

    private static @org.jetbrains.annotations.NotNull Class<?> getIdClass(Class<?> clazz) {
        Field idField = null;
        Class<?> current = clazz;
        while (current != null) {
            for (Field f : current.getDeclaredFields()) {
                if (f.isAnnotationPresent(ID.class)) {
                    idField = f;
                    break;
                }
            }
            if (idField != null) {
                break;
            }
            current = current.getSuperclass();
        }
        if (idField == null) {
            throw new IllegalStateException("No @ID field found in class hierarchy of " + clazz.getName());
        }
        //有可能是个基础类型 int 要转为 封装类型 Integer
        Class<?> idClass = TypeUtil.wrapPrimitive(idField.getType());
        return idClass;
    }

    /**
     * 热更重新加载
     */
    public void reload(List<Path> paths) {
        for (Path path : paths) {
            logger.info("资源热更开始 path:{}", path);
        }
        //先放到临时缓存
        Map<Class<?>, ResourceCache> tempCacheMap = new HashMap<>();
        for (Path path : paths) {
            Class<?> clazz = pathToClassMap.get(path);
            Preconditions.checkNotNull(clazz);
            ResourceCache tempResourceCache = new ResourceCache();
            initResourceBean(tempResourceCache, clazz);
            loadData(tempResourceCache);
            tempCacheMap.put(clazz, tempResourceCache);
        }

        check(tempCacheMap);

        //检查通过后 替换spring 缓存对象的配置数据
        for (Map.Entry<Class<?>, ResourceCache> e : tempCacheMap.entrySet()) {
            Class<?> clazz = e.getKey();
            ResourceCache tempCache = e.getValue();
            Path path = tempCache.getPath().normalize();
            ResourceCache resourceCache = resourceCacheMap.get(clazz);
            resourceCache.copyData(tempCache);
            logger.info("资源热更成功 path:{} class:{}", path, clazz);
            eventBus.publish(Event.newBuilder(new ResourceReloadedEvent(clazz)).build());
        }
    }

    public <K, V> ResourceCache getResourceCache(Class<V> clazz) {
        return resourceCacheMap.get(clazz);
    }

    public <K, V> V getResource(K key, Class<V> clazz) {
        ResourceCache cache = getResourceCache(clazz);
        if (cache == null) {
            return null;
        }
        return (V) cache.getResource(key);
    }


    private void init() {
        Preconditions.checkNotNull(packageInfo);
        Map<Class<?>, ResourceCache> resourceCacheMap = buildCacheMap(packageInfo);
        this.resourceCacheMap = resourceCacheMap;
        this.resourceCacheMap.entrySet().forEach(e -> {
            pathToClassMap.put(e.getValue().getPath(), e.getKey());
            reloadMonitor.registerReloadPath(e.getValue().getPath(), this::batchReload);
        });
    }

    private void batchReload(Path path) {
        synchronized (this) {
            batchReloadPaths.put(path, Boolean.TRUE);
        }
        MergeUpdateBox.MergeUpdateScheduler mergeUpdateScheduler = mergeUpdater.getMergeUpdateScheduler(this);
        mergeUpdateScheduler.tryMergeSchedule(idExecutorGroup, 1L, "配置热更加载", () -> {
            List<Path> paths = null;
            synchronized (ResourceService.this) {
                paths = batchReloadPaths.keySet().stream().collect(Collectors.toList());
                batchReloadPaths.clear();
            }
            try {
                reload(paths);
            } catch (Throwable t) {
                throw new RuntimeException("热更报错", t);
            }
        }, 200, TimeUnit.MILLISECONDS);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        setPackagePath(PropertiesUtil.getProperty("game.base-package"));
        init();
    }

    public ResourceCache<String, ? extends IGlobalItem> getGlobalResourceCache() {
        return globalResourceCache;
    }
}
